﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using System.Xml.XPath;
using Infrastructure.CrossCutting.Converters;
using Infrastructure.CrossCutting.Messages;

namespace Infrastructure.CrossCutting.Configuration
{
    public class ConfigurationParser
    {
        public static BoolMessageItem<T> Parse<T>()
        {
            var receiver = Activator.CreateInstance<T>();

            BoolMessage result = Parse(receiver);

            return result.Success
                       ? BoolMessageItem<T>.True.SetItem(receiver)
                       :BoolMessageItem<T>.False.SetMessage(result.Message);
        }

        public static BoolMessage Parse(object receiver)
        {
            IList<string> errors = new List<string>();
            object[] objs = receiver.GetType().GetCustomAttributes(typeof (ConfigurationFileAttribute), true);

            if(objs.Length > 0)
            {
                var configurationFile = objs[0] as ConfigurationFileAttribute;

                if(configurationFile != null)
                {
                    string itemWithSectionXPathExpression = string.Format("/{0}/{1}[@{2}=\"%SectionName%\"]/{3}[@{4}=\"%ItemName%\"]", 
                        configurationFile.RootElementName, 
                        configurationFile.SectionElementName, 
                        configurationFile.SectionNameAttributeName, 
                        configurationFile.ItemElementName,
                        configurationFile.ItemKeyAttributeName);

                    string itemWithoutSectionXPathExpression = string.Format("/{0}/{1}[@{2}=\"%ItemName%\"]", 
                        configurationFile.RootElementName,
                        configurationFile.ItemElementName,
                        configurationFile.ItemKeyAttributeName);
                    
                    if(!File.Exists(configurationFile.ConfigFilePath))
                        throw new FileNotFoundException(string.Format("Configuration file {0} not found.", configurationFile.ConfigFilePath));

                    XDocument configDocument = XDocument.Load(configurationFile.ConfigFilePath);

                    foreach (var propertyInfo in receiver.GetType().GetProperties())
                    {
                        object[] props = propertyInfo.GetCustomAttributes(typeof (ConfigurationPropertyAttribute), true);
                        if(props.Length > 0)
                        {
                            var configurationProperty = props[0] as ConfigurationPropertyAttribute;
                            if(configurationProperty != null)
                            {
                                string sectionName = configurationProperty.SectionName;
                                string name = configurationProperty.Name;

                                string xpath = string.IsNullOrEmpty(sectionName)
                                                   ? itemWithoutSectionXPathExpression.Replace("%ItemName%", name)
                                                   : itemWithSectionXPathExpression.Replace("%ItemName%", name).Replace("%SectionName%", sectionName);

                                string value = null;

                                XElement element = configDocument.XPathSelectElement(xpath);

                                if (element != null)
                                {
                                    XAttribute valueAttribute = element.Attribute(
                                        configurationFile.ItemValueAttributeName);

                                    if(valueAttribute != null)
                                        value = valueAttribute.Value;
                                }

                                if(string.IsNullOrEmpty(value))
                                    value = configurationProperty.DefaultValue;

                                if(string.IsNullOrEmpty(value))
                                {
                                    errors.Add(string.IsNullOrEmpty(sectionName)
                                                   ? string.Format("Required Configuration Item '{0}' is missing.", name)
                                                   : string.Format("Required Configuration Item '{0}:{1}' is missing.",
                                                                   sectionName, name));
                                    continue;
                                }

                                Type valueType = propertyInfo.PropertyType;

                                if (!DataTypeConverter.CanConvertTo(valueType, value))
                                    errors.Add(string.Format("Config value of '{0}' does not match type {1}.",
                                      name, valueType));
                                else
                                    propertyInfo.SetValue(receiver, DataTypeConverter.ConvertTo(valueType, value));
                            }
                        }
                    }
                }
            }

            return errors.Count == 0 
            ? BoolMessage.True
            : BoolMessage.False.SetMessage(string.Join(Environment.NewLine, errors));
        } 
    }
}
